import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * 三数之和
 * 题目：给你一个包含 n 个整数的数组nums，判断nums中是否存在三个元素 a，b，c ，使得a + b + c = 0 ？请你找出所有满足条件且不重复的三元组。
 * <p>
 * 注意：答案中不可以包含重复的三元组。
 * <p>
 * 示例：
 * 给定数组 nums = [-1, 0, 1, 2, -1, -4]，
 * <p>
 * 满足要求的三元组集合为：
 * [
 * [-1, 0, 1],
 * [-1, -1, 2]
 * ]
 * <p>
 * 来源：力扣（LeetCode-15）
 * 链接：https://leetcode-cn.com/problems/3sum
 *
 * @author godfrey
 * @since 2020-11-02
 */
@DisplayName("三数之和")
public class ThreeSum extends BaseTest {

    static Stream<Arguments> testArguments() {
        return Stream.of(
                Arguments.arguments((Object) new int[]{-1, 0, 1, 2, -1, -4}),
                Arguments.arguments((Object) new int[]{-4, -2, 1, -5, -4, -4, 4, -2, 0, 4, 0, -2, 3, 1, -5, 0}),
                Arguments.arguments((Object) new int[]{-1, 0, 1, 2, -1, -4, -2, -3, 3, 0, 4})
        );
    }

    @DisplayName("左右夹逼(双指针)-时间复杂度O(n^2)，空间复杂度O(1)")
    @ParameterizedTest
    @MethodSource("testArguments")
    void threeSum(int[] nums) {
        final int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len < 3) {
            System.out.println(res);
            return;
        }
        //先排序
        Arrays.sort(nums);
        final int target = 0;
        int l, r, sum;
        for (int i = 0; i < len - 2; ++i) {
            if (nums[i] > 0) {
                break; // 如果当前数字大于0，则三数之和一定大于0，所以结束循环
            }
            //重复元素num[i]
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            l = i + 1;
            r = len - 1;
            sum = nums[i] + nums[l] + nums[r];
            while (l < r) {
                if (sum < target) {
                    ++l;
                    //重复元素num[l]
                    while (nums[l] == nums[l - 1] && l < r) {
                        ++l;
                    }
                } else if (sum > target) {
                    --r;
                    //重复元素num[r]
                    while (nums[r] == nums[r + 1] && l < r) {
                        --r;
                    }
                } else {
                    res.add(Arrays.asList(nums[i], nums[l], nums[r]));
                    ++l;
                    --r;
                    //重复元素num[l]
                    while (nums[l] == nums[l - 1] && l < r) {
                        ++l;
                    }
                    //重复元素num[r]
                    while (nums[r] == nums[r + 1] && l < r) {
                        --r;
                    }
                }
            }
        }
        System.out.println(res);
    }
}
